Components › Navigation › Section top bar › Settings page

Settings page

canon

Settings screen: left rail menu with search, section top bar and a 660px content column (Attio pattern).

Lives in apps/web (SettingsLayout). Menu = SETTINGS_NAV, one icon per item, sections ≤660px, no two tab levels (RULES §12).

Used in 1 file · 1 area · 3× — App

Keyboard

  • The rail is a real <nav className="set-nav"> landmark; every group and item is a plain <button>, so Tab walks search → group 1 items → group 2 items → … in document order, and Enter/Space activates the focused item like any button.
  • The right column's SectionTopBar and its content follow after the rail in tab order (rail is the first child of .set-shell, content the second).
  • On phone, the back-to-menu control (IconButton icon="arrow-left") is the first focusable element of the section's SectionTopBar when a section is open; the rail's own back button (.set-back, leaves Settings entirely) only exists on the menu step.
  • No custom key handling is added for the rail itself (no roving tabindex, no arrow-key navigation between items) — it behaves as a plain sequential list of buttons, not a listbox.

ARIA

  • The active rail item gets aria-current="page" (SettingsLayout.tsx: aria-current={!menuOnly && i.key === railKey ? 'page' : undefined}), cleared while the phone menu step is showing (menuOnly) since no item is "current" there.
  • The search input has aria-label={t('search')}; the placeholder is not relied on as the name.
  • The rail's own back button (leave Settings) has aria-label={t('back')}; the phone back-to-menu button has its own aria-label={t('backToMenu')} — the two are different actions and carry different labels, not a shared generic "Back".
  • A canvas section (Uživatelé, Týmy, AI zaměstnanci, Import, Koš, Aktivita) supplies its own heading/landmark structure inside .set-canvas; the settings shell does not add a duplicate heading around it.
  • The SectionTopBar identity (icon + section name) is text, not an <h1> — same contract as section-top-bar/title-with-entity-tile; the page does not add its own heading level either.

Focus

  • After a section switch focus moves to the section's bar title (tabIndex={-1}), so a screen reader announces the new page (acrm !4027, 27 Sep 2026). Canvas sections move focus to the canvas instead. On phone, "back" returns focus to the menu item the user came from. The first load does not steal focus. Covered by a test (4 cases).
  • Rename/inline-edit and dialog focus inside individual settings sections follow those components' own contracts (e.g. Dialog, formless-dialog), not this shell.

Contrast

Not independently re-measured for this page — it reuses the same tokens already measured for the components it is built from: rail item text/icon = --fg-secondary / --fg (as in sidebar-nav-item), active row = --active-surface, section identity text = as documented on section-top-bar / title-with-entity-tile. See those pages for the measured ratios.

Known limits

  • The rail has no role="navigation" label distinguishing it from other landmarks if the page is later embedded somewhere with more than one <nav> (today it is the only one on the route, since SettingsLayout renders full-screen outside AppShell).
  • Group headers (.set-grp) are plain text, not <h2>/<h3> — a screen-reader user cannot jump rail groups by heading navigation, only by reading the list in order.
  • Canvas sections and their own a11y contracts are out of scope for this page — check the section's own component (e.g. UzivatelePage, TymyPage, ImportPage) for its own gaps.

Source: docs/section-top-bar/settings-page.a11y.md · GitLab